package com.dlc.shop.seckill.common.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dlc.shop.bean.app.vo.SeckillVO;
import com.dlc.shop.bean.enums.EsOperationType;
import com.dlc.shop.bean.enums.OfflineHandleEventStatus;
import com.dlc.shop.bean.enums.OfflineHandleEventType;
import com.dlc.shop.bean.enums.ProdType;
import com.dlc.shop.bean.event.EsProductUpdateEvent;
import com.dlc.shop.bean.event.RemoveDiscountProdByIdsEvent;
import com.dlc.shop.bean.model.OfflineHandleEvent;
import com.dlc.shop.bean.model.ProdLang;
import com.dlc.shop.bean.model.Product;
import com.dlc.shop.bean.param.OfflineHandleEventAuditParam;
import com.dlc.shop.bean.param.ProductParam;
import com.dlc.shop.common.constants.CacheNames;
import com.dlc.shop.common.enums.StatusEnum;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.util.PageParam;
import com.dlc.shop.common.util.RedisUtil;
import com.dlc.shop.manager.impl.LangManager;
import com.dlc.shop.seckill.common.dao.SeckillMapper;
import com.dlc.shop.seckill.common.dto.SeckillPageDto;
import com.dlc.shop.seckill.common.enums.SeckillEnum;
import com.dlc.shop.seckill.common.model.Seckill;
import com.dlc.shop.seckill.common.model.SeckillSku;
import com.dlc.shop.seckill.common.service.SeckillService;
import com.dlc.shop.seckill.common.service.SeckillSkuService;
import com.dlc.shop.service.OfflineHandleEventService;
import com.dlc.shop.service.ProductService;
import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 秒杀信息
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@Service
@AllArgsConstructor
public class SeckillServiceImpl extends ServiceImpl<SeckillMapper, Seckill> implements SeckillService {

    private final SeckillMapper seckillMapper;

    private final SeckillSkuService seckillSkuService;

    private final ProductService productService;

    private final OfflineHandleEventService offlineHandleEventService;

    private final ApplicationContext applicationContext;

    private final ApplicationEventPublisher eventPublisher;

    private final LangManager langManager;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSeckill(Seckill seckill, List<SeckillSku> seckillSkus) {

        seckill.setStatus(1);
        seckill.setIsDelete(0);
        // 保存秒杀信息
        seckillMapper.insert(seckill);

        // 这里再查下秒杀商品对应的活动数量，如果大于1，就证明已经有一个跟该商品对应的活动了，本次操作不应该继续下去
        Long seckillNum = seckillMapper.selectCount(new LambdaQueryWrapper<Seckill>()
                .eq(Seckill::getProdId, seckill.getProdId())
                .eq(Seckill::getIsDelete, 0)
                .ge(Seckill::getStatus, StatusEnum.ENABLE.value()));
        if(seckillNum > 1) {
            // 该商品已创建秒杀活动，请勿重复提交
            throw new YamiShopBindException("yami.product.has.created.seckill");
        }

        seckillSkus.forEach(seckillSku -> {
            seckillSku.setSeckillId(seckill.getSeckillId());
            seckillSku.setSeckillOriginStocks(seckillSku.getSeckillStocks());
        });

        // 保存秒杀sku信息
        seckillSkuService.saveSeckillSkuStockBatch(seckillSkus);

        // 更新商品信息
        Product product = new Product();
        product.setActivityId(seckill.getSeckillId());
        product.setProdType(ProdType.PROD_TYPE_SECKILL.value());
        product.setProdId(seckill.getProdId());
        productService.updateById(product);
        // 发送事件，清除掉如果是限时特惠中的可用商品
        applicationContext.publishEvent(new RemoveDiscountProdByIdsEvent(Collections.singletonList(seckill.getProdId())));
    }

    /**
     * 这里的缓存时间参考RedisCacheConfig#getRedisCacheConfigurationMap
     * @param seckillId
     * @return
     */
    @Override
    @Cacheable(cacheNames = "SeckillById", key = "#seckillId")
    public Seckill getSeckillById(Long seckillId) {
        return seckillMapper.selectById(seckillId);
    }

    @Override
    @Cacheable(cacheNames = "SeckillByProdId", key = "#prodId")
    public Seckill getSeckillByProdId(Long prodId) {
        return seckillMapper.selectOne(new LambdaQueryWrapper<Seckill>()
                .eq(Seckill::getProdId, prodId)
                .eq(Seckill::getStatus, StatusEnum.ENABLE.value())
        );
    }

    @Override
    @Caching(evict = {
            @CacheEvict(cacheNames = "SeckillById", key = "#seckillId"),
            @CacheEvict(cacheNames = "SeckillByProdId", key = "#prodId")
    })
    public void removeSeckillCache(Long seckillId, Long prodId) {
    }

    @Override
    public void batchRemoveSeckillCache(List<Long> seckillIds, List<Long> prodIds) {
        List<String> keys = new ArrayList<>();
        if (CollUtil.isNotEmpty(prodIds)) {
            for (Long prodId : prodIds) {
                keys.add("SeckillByProdId" + CacheNames.UNION + prodId);
            }
        }
        if (CollUtil.isEmpty(seckillIds)) {
            return;
        }
        for (Long seckillId : seckillIds) {
            keys.add("SeckillById" + CacheNames.UNION + seckillId);
        }
        RedisUtil.del(keys);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invalidById(Seckill seckill) {

        seckill.setStatus(0);
        seckillMapper.updateById(seckill);
        Product productParam = new Product();
        productParam.setProdId(seckill.getProdId());
        productParam.setActivityId(0L);
        productParam.setProdType(ProdType.PROD_TYPE_NORMAL.value());
        Seckill newSeckill = getByProdId(seckill.getProdId());
        Product product = productService.getProductByProdId(seckill.getProdId());
        if (Objects.isNull(newSeckill)
                && (Objects.equals(product.getProdType(), ProdType.PROD_TYPE_GROUP.value())
            || Objects.equals(product.getProdType(), ProdType.PROD_TYPE_SECKILL.value()))) {
            productService.updateById(productParam);
        }
        eventPublisher.publishEvent(new EsProductUpdateEvent(seckill.getProdId(), null, EsOperationType.UPDATE));
    }

    @Override
    public IPage<SeckillPageDto> getPlatformSeckillPage(PageParam<Seckill> page, Seckill seckill) {
        IPage<SeckillPageDto> platformSeckillPage = seckillMapper.getPlatformSeckillPage(page, seckill);
        List<Long> prodIds = platformSeckillPage.getRecords().stream().map(SeckillPageDto::getProdId).filter(prodId -> Objects.nonNull(prodId)).collect(Collectors.toList());
        Map<Long, ProdLang> prodLangMap = langManager.getProdLangMap(prodIds);
        for (SeckillPageDto seckillPageDto : platformSeckillPage.getRecords()) {
            ProdLang prodLang = prodLangMap.get(seckillPageDto.getProdId());
            if (Objects.nonNull(prodLang)) {
                seckillPageDto.setProdName(prodLang.getProdName());
            }
        }
        return platformSeckillPage;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void offline(Seckill seckill, String offlineReason, Long sysUserId) {

        // 添加下线处理记录
        Date now = new Date();
        OfflineHandleEvent offlineHandleEvent = new OfflineHandleEvent();
        offlineHandleEvent.setHandleId(seckill.getSeckillId());
        offlineHandleEvent.setCreateTime(now);
        offlineHandleEvent.setHandleType(OfflineHandleEventType.SECKILL.getValue());
        offlineHandleEvent.setHandlerId(sysUserId);
        offlineHandleEvent.setShopId(seckill.getShopId());
        offlineHandleEvent.setOfflineReason(offlineReason);
        offlineHandleEvent.setStatus(OfflineHandleEventStatus.OFFLINE_BY_PLATFORM.getValue());
        offlineHandleEvent.setUpdateTime(now);
        offlineHandleEventService.save(offlineHandleEvent);

        // 更新活动状态为下线
        seckillMapper.updateStatus(seckill.getSeckillId(), SeckillEnum.OFFLINE.getValue());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditSeckill(OfflineHandleEventAuditParam offlineHandleEventAuditParam, Long sysUserId) {
        // 审核通过
        if (Objects.equals(offlineHandleEventAuditParam.getStatus(), OfflineHandleEventStatus.AGREE_BY_PLATFORM.getValue())) {
            // 更新秒杀活动为正常状态
            seckillMapper.updateStatus(offlineHandleEventAuditParam.getHandleId(), SeckillEnum.NORMAL.getValue());
        }
        // 审核不通过
        else if (Objects.equals(offlineHandleEventAuditParam.getStatus(), OfflineHandleEventStatus.DISAGREE_BY_PLATFORM.getValue())) {
            seckillMapper.updateStatus(offlineHandleEventAuditParam.getHandleId(), SeckillEnum.OFFLINE.getValue());
        }
        // 更新审核时间
        offlineHandleEventService.auditOfflineEvent(offlineHandleEventAuditParam, sysUserId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditApply(Long eventId, Long seckillId, String reapplyReason) {
        // 更新活动为待审核状态
        seckillMapper.updateStatus(seckillId, SeckillEnum.WAIT_AUDIT.getValue());

        // 更新事件状态
        offlineHandleEventService.updateToApply(eventId, reapplyReason);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changeProdTypeBySeckillIdList(List<Long> seckillIds) {
        seckillMapper.changeProdTypeBySeckillIdList(seckillIds);
        seckillMapper.changeSeckillStatusBySeckillIdList(seckillIds);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Seckill> invalidByShopId(Long shopId) {
        // 获取正在进行中的秒杀活动
        List<Seckill> seckills = seckillMapper.selectList(Wrappers.lambdaQuery(Seckill.class)
                .select(Seckill::getSeckillId, Seckill::getProdId)
                .eq(Seckill::getShopId, shopId)
                .eq(Seckill::getIsDelete, 0)
                .eq(Seckill::getStatus, SeckillEnum.NORMAL.getValue())
        );
        if (CollUtil.isEmpty(seckills)) {
            return new ArrayList<>();
        }
        List<Long> seckillIds = new ArrayList<>();
        List<Long> prodIds = new ArrayList<>();
        for (Seckill seckill : seckills) {
            seckillIds.add(seckill.getSeckillId());
            prodIds.add(seckill.getProdId());
        }
        // 将商品变回普通商品
        productService.update(Wrappers.lambdaUpdate(Product.class)
                .set(Product::getActivityId, 0L)
                .set(Product::getProdType, ProdType.PROD_TYPE_NORMAL.value())
                .in(Product::getProdId, prodIds)
        );
        // 失效秒杀活动
        update(Wrappers.lambdaUpdate(Seckill.class)
                .set(Seckill::getStatus, SeckillEnum.INVALID.getValue())
                .in(Seckill::getSeckillId, seckillIds)
        );
        // 更新es中的商品数据
        eventPublisher.publishEvent(new EsProductUpdateEvent(null, prodIds, EsOperationType.UPDATE_BATCH));
        return seckills;
    }

    @Override
    @Cacheable(cacheNames = "SeckillGetByProdId", key = "#prodId", sync = true)
    public Seckill getByProdId(Long prodId) {
        return seckillMapper.selectOne(new LambdaQueryWrapper<Seckill>().eq(Seckill::getProdId, prodId).eq(Seckill::getStatus, SeckillEnum.NORMAL.getValue()));
    }

    @Override
    public void updateSeckillStocks(Long seckillId, int changeStock) {
        seckillMapper.updateSeckillStocks(seckillId, changeStock);
    }

    @Override
    public IPage<Product> pageSeckillNormalProd(PageParam<Product> page, ProductParam product) {
        IPage<Product> productPage = seckillMapper.pageSeckillNormalProd(page, product);
        langManager.getProdLang(productPage.getRecords());
        return productPage;
    }

    @Override
    public IPage<SeckillPageDto> pageSeckill(PageParam<Seckill> page, SeckillVO seckillVO) {
        IPage<SeckillPageDto> seckillPage = seckillMapper.pageSeckill(page, seckillVO);
        if (CollUtil.isEmpty(seckillPage.getRecords())) {
            return seckillPage;
        }
        List<Long> ids = seckillPage.getRecords().stream().map(SeckillPageDto::getProdId).filter(prodId -> Objects.nonNull(prodId)).collect(Collectors.toList());
        Map<Long, ProdLang> prodLangMap = langManager.getProdLangMap(ids);
        for (SeckillPageDto item : seckillPage.getRecords()) {
            ProdLang prodLang = prodLangMap.get(item.getProdId());
            if (Objects.nonNull(prodLang)) {
                item.setProdName(prodLang.getProdName());
            }
        }
        return seckillPage;
    }

}
